home *** CD-ROM | disk | FTP | other *** search
/ PC Format (PL) 2008 February / PC_Format_022008.iso / Internet / Mozilla Thunderbird wtyczki / lightning-0.7-tb-win.xpi / js / calOutlookCSVImportExport.js < prev    next >
Encoding:
JavaScript  |  2007-08-17  |  16.6 KB  |  458 lines

  1. /* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  2.  * ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is Mozilla Calendar code.
  16.  *
  17.  * The Initial Developer of the Original Code is
  18.  * Jussi Kukkonen <jussi.kukkonen@welho.com>.
  19.  * Portions created by the Initial Developer are Copyright (C) 2004
  20.  * the Initial Developer. All Rights Reserved.
  21.  *
  22.  * Contributor(s):
  23.  *   Michiel van Leeuwen <mvl@exedo.nl>
  24.  *
  25.  * Alternatively, the contents of this file may be used under the terms of
  26.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  27.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28.  * in which case the provisions of the GPL or the LGPL are applicable instead
  29.  * of those above. If you wish to allow use of your version of this file only
  30.  * under the terms of either the GPL or the LGPL, and not to allow others to
  31.  * use your version of this file under the terms of the MPL, indicate your
  32.  * decision by deleting the provisions above and replace them with the notice
  33.  * and other provisions required by the GPL or the LGPL. If you do not delete
  34.  * the provisions above, a recipient may use your version of this file under
  35.  * the terms of any one of the MPL, the GPL or the LGPL.
  36.  *
  37.  * ***** END LICENSE BLOCK ***** */
  38.  
  39. // Import
  40.  
  41. function calOutlookCSVImporter() {
  42.     this.wrappedJSObject = this;
  43. }
  44.  
  45. calOutlookCSVImporter.prototype.QueryInterface =
  46. function QueryInterface(aIID) {
  47.     if (!aIID.equals(Components.interfaces.nsISupports) &&
  48.         !aIID.equals(Components.interfaces.calIImporter)) {
  49.         throw Components.results.NS_ERROR_NO_INTERFACE;
  50.     }
  51.  
  52.     return this;
  53. };
  54.  
  55. function getOutlookCsvFileTypes(aCount) {
  56.     aCount.value = 1;
  57.     var wildmat = '*.csv';
  58.     var label = calGetString("calendar", 'filterOutlookCsv', [wildmat]);
  59.     return([{defaultExtension:'csv', 
  60.              extensionFilter: wildmat, 
  61.              description: label}]);
  62. }
  63.  
  64. calOutlookCSVImporter.prototype.getFileTypes = getOutlookCsvFileTypes;
  65.  
  66. const localeEn = {
  67.     headTitle       : "Subject",
  68.     headStartDate   : "Start Date",
  69.     headStartTime   : "Start Time",
  70.     headEndDate     : "End Date",
  71.     headEndTime     : "End Time",
  72.     headAllDayEvent : "All day event",
  73.     headAlarm       : "Reminder on/off",
  74.     headAlarmDate   : "Reminder Date",
  75.     headAlarmTime   : "Reminder Time",
  76.     headCategories  : "Categories",
  77.     headDescription : "Description",
  78.     headLocation    : "Location",
  79.     headPrivate     : "Private",
  80.  
  81.     valueTrue       : "True",
  82.     valueFalse      : "False",
  83.     
  84.     dateRe          : /^(\d+)\/(\d+)\/(\d+)$/,
  85.     dateDayIndex    : 2,
  86.     dateMonthIndex  : 1,
  87.     dateYearIndex   : 3,
  88.     dateFormat      : "%m/%d/%y",
  89.  
  90.     timeRe          : /^(\d+):(\d+):(\d+) (\w+)$/,
  91.     timeHourIndex   : 1,
  92.     timeMinuteIndex : 2,
  93.     timeSecondIndex : 3,
  94.     timeAmPmIndex   : 4,
  95.     timeAmString    : "AM",
  96.     timePmString    : "PM",
  97.     timeFormat      : "%I:%M:%S %p"
  98. };
  99.  
  100. const localeNl = {
  101.     headTitle       : "Onderwerp",
  102.     headStartDate   : "Begindatum",
  103.     headStartTime   : "Begintijd",
  104.     headEndDate     : "Einddatum",
  105.     headEndTime     : "Eindtijd",
  106.     headAllDayEvent : "Evenement, duurt hele dag",
  107.     headAlarm       : "Herinneringen aan/uit",
  108.     headAlarmDate   : "Herinneringsdatum",
  109.     headAlarmTime   : "Herinneringstijd",
  110.     headCategories  : "Categorieδn",
  111.     headDescription : "Beschrijving",
  112.     headLocation    : "Locatie",
  113.     headPrivate     : "PrivΘ",
  114.  
  115.     valueTrue       : "Waar",
  116.     valueFalse      : "Onwaar",
  117.     
  118.     dateRe          : /^(\d+)-(\d+)-(\d+)$/,
  119.     dateDayIndex    : 1,
  120.     dateMonthIndex  : 2,
  121.     dateYearIndex   : 3,
  122.     dateFormat      : "%d-%m-%y",
  123.  
  124.     timeRe          : /^(\d+):(\d+):(\d+)$/,
  125.     timeHourIndex   : 1,
  126.     timeMinuteIndex : 2,
  127.     timeSecondIndex : 3,
  128.     timeFormat      : "%H:%M:%S"
  129. };
  130.  
  131. const locales = [localeEn, localeNl];
  132.  
  133. /**
  134.  * Takes a text block of Outlook-exported Comma Separated Values and tries to 
  135.  * parse that into individual events.  
  136.  * 
  137.  * First line is field names, all quoted with double quotes.  Field names are
  138.  * locale dependendent.  In English the recognized field names are:
  139.  *   "Title","Start Date","Start Time","End Date","End Time","All day event",
  140.  *   "Reminder on/off","Reminder Date","Reminder Time","Categories",
  141.  *   "Description","Location","Private"
  142.  * Not all fields are necessary.  If some fields do not match known field names,
  143.  * a dialog is presented to the user to match fields.
  144.  * 
  145.  * The rest of the lines are events, one event per line, with fields in the
  146.  * order descibed by the first line.   All non-empty values must be quoted.
  147.  * 
  148.  * Returns: an array of parsed calendarEvents.  
  149.  *   If the parse is cancelled, a zero length array is returned.
  150.  */ 
  151.  
  152. calOutlookCSVImporter.prototype.importFromStream =
  153. function csv_importFromStream(aStream, aCount) {
  154.     var scriptableInputStream = Components.classes["@mozilla.org/scriptableinputstream;1"]
  155.                                       .createInstance(Components.interfaces.nsIScriptableInputStream);
  156.     scriptableInputStream.init(aStream);
  157.     var str = scriptableInputStream.read(-1);
  158.  
  159.     parse: { 
  160.         // parse header line of quoted comma separated column names.
  161.         var trimEndQuotesRegExp = /^"(.*)"$/m;
  162.         var trimResults = trimEndQuotesRegExp.exec( str );
  163.         var header = trimResults && trimResults[1].split(/","/);
  164.         if (header == null)
  165.             break parse;
  166.  
  167.         //strip header from string
  168.         str = str.slice(trimResults[0].length);
  169.  
  170.         var args = new Object();
  171.         //args.fieldList contains the field names from the first row of CSV
  172.         args.fieldList = header; 
  173.  
  174.         var locale;  
  175.         var i;
  176.         for (i in locales) {
  177.             locale = locales[i];
  178.             var knownIndxs = 0;
  179.             for (var i = 1; i <= header.length; ++i) {
  180.                 switch( header[i-1] ) {
  181.                     case locale.headTitle:        args.titleIndex = i;       knownIndxs++; break;
  182.                     case locale.headStartDate:    args.startDateIndex = i;   knownIndxs++; break;
  183.                     case locale.headStartTime:    args.startTimeIndex = i;   knownIndxs++; break;
  184.                     case locale.headEndDate:      args.endDateIndex = i;     knownIndxs++; break;
  185.                     case locale.headEndTime:      args.endTimeIndex = i;     knownIndxs++; break;
  186.                     case locale.headAllDayEvent:  args.allDayIndex = i;      knownIndxs++; break;
  187.                     case locale.headAlarm:        args.alarmIndex = i;       knownIndxs++; break;
  188.                     case locale.headAlarmDate:    args.alarmDateIndex = i;   knownIndxs++; break;
  189.                     case locale.headAlarmTime:    args.alarmTimeIndex = i;   knownIndxs++; break;
  190.                     case locale.headCategories:   args.categoriesIndex = i;  knownIndxs++; break;
  191.                     case locale.headDescription:  args.descriptionIndex = i; knownIndxs++; break;
  192.                     case locale.headLocation:     args.locationIndex = i;    knownIndxs++; break;
  193.                     case locale.headPrivate:      args.privateIndex = i;     knownIndxs++; break;
  194.                 }
  195.             }
  196.             if (knownIndxs >= 13)
  197.                 break;
  198.         }
  199.  
  200.         if (knownIndxs == 0 && header.length == 22) {
  201.             // set default indexes for a default Outlook2000 CSV file
  202.             args.titleIndex = 1;
  203.             args.startDateIndex = 2;
  204.             args.startTimeIndex = 3;
  205.             args.endDateIndex = 4;
  206.             args.endTimeIndex = 5;
  207.             args.allDayIndex = 6;
  208.             args.alarmIndex = 7;
  209.             args.alarmDateIndex = 8;
  210.             args.alarmTimeIndex = 9;
  211.             args.categoriesIndex = 15;
  212.             args.descriptionIndex = 16;
  213.             args.locationIndex = 17;
  214.             args.privateIndex = 20;
  215.         }  
  216.  
  217.         // show field select dialog if not all required headers matched
  218.         if (knownIndxs < 13) {
  219.             dump("Can't import. Life sucks\n")
  220.             break parse;
  221.         }
  222.  
  223.         // Construct event regexp according to field indexes. The regexp can
  224.         // be made stricter, if it seems this matches too loosely.
  225.         var regExpStr = "^";
  226.         for (i = 1; i <= header.length; i++) {
  227.             if (i > 1)
  228.                 regExpStr += ",";
  229.             regExpStr += "(?:\"((?:[^\"]|\"\")*)\")?"; 
  230.         }
  231.         regExpStr += "$";
  232.  
  233.         // eventRegExp: regexp for reading events (this one'll be constructed on fly)
  234.         const eventRegExp = new RegExp(regExpStr, "gm");
  235.  
  236.         // match first line
  237.         var eventFields = eventRegExp(str);
  238.  
  239.         if (eventFields == null)
  240.             break parse;
  241.  
  242.         args.boolStr = localeEn.valueTrue;
  243.         args.boolIsTrue = true; 
  244.  
  245.         var dateParseConfirmed = false;
  246.         var eventArray = new Array();
  247.         do {
  248.             // At this point eventFields contains following fields. Position
  249.             // of fields is in args.[fieldname]Index.
  250.             //    subject, start date, start time, end date, end time,
  251.             //    all day?, alarm?, alarm date, alarm time,
  252.             //    Description, Categories, Location, Private?
  253.             // Unused fields (could maybe be copied to Description):
  254.             //    Meeting Organizer, Required Attendees, Optional Attendees,
  255.             //    Meeting Resources, Billing Information, Mileage, Priority,
  256.             //    Sensitivity, Show time as
  257.  
  258.             var title = ("titleIndex" in args
  259.                          ? parseTextField(eventFields[args.titleIndex]) : "");
  260.             var sDate = parseDateTime(eventFields[args.startDateIndex],
  261.                                       eventFields[args.startTimeIndex],
  262.                                       locale);
  263.             var eDate = parseDateTime(eventFields[args.endDateIndex],
  264.                                       eventFields[args.endTimeIndex],
  265.                                       locale);
  266.             var alarmDate = parseDateTime(eventFields[args.alarmDateIndex],
  267.                                           eventFields[args.alarmTimeIndex],
  268.                                           locale);
  269.             if (title || sDate) {
  270.                 var event = Components.classes["@mozilla.org/calendar/event;1"]
  271.                                       .createInstance(Components.interfaces.calIEvent);
  272.  
  273.                 event.title = title;
  274.                 if (sDate) {
  275.                     sDate.isDate = (locale.valueTrue == eventFields[args.allDayIndex]);
  276.                 }
  277.                 if (locale.valueTrue == eventFields[args.privateIndex])
  278.                     event.privacy = "PRIVATE";
  279.  
  280.                 if (!eDate && sDate) {
  281.                     eDate = sDate.clone();
  282.                     if (sDate.isDate) {
  283.                         // end date is exclusive, so set to next day after start.
  284.                        eDate.day = eDate.day + 1;
  285.                     }
  286.                 }
  287.                 if (sDate) 
  288.                     event.startDate = sDate;
  289.                 if (eDate) 
  290.                     event.endDate = eDate;
  291.  
  292.                 if (alarmDate) {
  293.                     event.alarmOffset = sDate.subtractDate(alarmDate);
  294.                     event.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_START;
  295.                 }
  296.  
  297.                 if ("descriptionIndex" in args)
  298.                     event.setProperty("DESCRIPTION", parseTextField(eventFields[args.descriptionIndex]));
  299.                 if ("categoriesIndex" in args)
  300.                     event.setProperty("CATEGORIES", parseTextField(eventFields[args.categoriesIndex]));
  301.                 if ("locationIndex" in args)
  302.                     event.setProperty("LOCATION", parseTextField(eventFields[args.locationIndex]));
  303.  
  304.                 //save the event into return array
  305.                 eventArray.push(event);
  306.             }
  307.  
  308.             //get next events fields
  309.             eventFields = eventRegExp(str);
  310.  
  311.         } while (eventRegExp.lastIndex != 0);
  312.  
  313.         // return results
  314.         aCount.value = eventArray.length;
  315.         return eventArray;
  316.  
  317.     } // end parse
  318.  
  319.     aCount.value = 0;
  320.     return new Array();
  321. };
  322.  
  323. function parseDateTime(aDate, aTime, aLocale)
  324. {
  325.     var date = Components.classes["@mozilla.org/calendar/datetime;1"]
  326.                          .createInstance(Components.interfaces.calIDateTime);
  327.  
  328.     //XXX Can we do better?
  329.     date.timezone = "floating";
  330.  
  331.     var rd = aLocale.dateRe.exec(aDate);
  332.     var rt = aLocale.timeRe.exec(aTime);
  333.  
  334.     if (!rd || !rt) {
  335.         return null;
  336.     }
  337.     
  338.     date.year = rd[aLocale.dateYearIndex];
  339.     date.month = rd[aLocale.dateMonthIndex] - 1;
  340.     date.day = rd[aLocale.dateDayIndex];
  341.     if (rt) {
  342.         date.hour = Number(rt[aLocale.timeHourIndex]);
  343.         date.minute = rt[aLocale.timeMinuteIndex];
  344.         date.second = rt[aLocale.timeSecondIndex];
  345.     } else {
  346.         date.isDate = true;
  347.     }
  348.  
  349.     if (rt && aLocale.timeAmPmIndex)
  350.       if (rt[aLocale.timeAmPmIndex] != aLocale.timePmString) {
  351.         // AM
  352.         if (date.hour == 12)
  353.           date.hour = 0;
  354.       } else {
  355.         // PM
  356.          if (date.hour < 12)
  357.           date.hour += 12;
  358.     }
  359.  
  360.     dump(date+"\n");
  361.     return date;
  362. }
  363.  
  364. function parseTextField(aTextField)
  365. {
  366.   return aTextField ? aTextField.replace(/""/g, "\"") : "";
  367. }
  368.  
  369.  
  370. // Export
  371.  
  372. function calOutlookCSVExporter() {
  373.     this.wrappedJSObject = this;
  374. }
  375.  
  376. calOutlookCSVExporter.prototype.QueryInterface =
  377. function QueryInterface(aIID) {
  378.     if (!aIID.equals(Components.interfaces.nsISupports) &&
  379.         !aIID.equals(Components.interfaces.calIExporter)) {
  380.         throw Components.results.NS_ERROR_NO_INTERFACE;
  381.     }
  382.  
  383.     return this;
  384. };
  385.  
  386. calOutlookCSVExporter.prototype.getFileTypes = getOutlookCsvFileTypes;
  387.  
  388. // not prototype.export. export is reserved.
  389. calOutlookCSVExporter.prototype.exportToStream =
  390. function csv_exportToStream(aStream, aCount, aItems) {
  391.     var str = "";
  392.     var headers = [];
  393.     // Not using a loop here, since we need to be sure the order here matches
  394.     // with the orders the field data is added later on
  395.     headers.push(localeEn['headTitle']);
  396.     headers.push(localeEn['headStartDate']);
  397.     headers.push(localeEn['headStartTime']);
  398.     headers.push(localeEn['headEndDate']);
  399.     headers.push(localeEn['headEndTime']);
  400.     headers.push(localeEn['headAllDayEvent']);
  401.     headers.push(localeEn['headAlarm']);
  402.     headers.push(localeEn['headAlarmDate']);
  403.     headers.push(localeEn['headAlarmTime']);
  404.     headers.push(localeEn['headCategories']);
  405.     headers.push(localeEn['headDescription']);
  406.     headers.push(localeEn['headLocation']);
  407.     headers.push(localeEn['headPrivate']);
  408.     str = headers.map(function(v) {return '"'+v+'"';}).join(',')+"\n"
  409.     aStream.write(str, str.length);
  410.  
  411.     for each (item in aItems) {
  412.         var line = [];
  413.         line.push(item.title);
  414.         line.push(dateString(item.startDate));
  415.         line.push(timeString(item.startDate));
  416.         line.push(dateString(item.endDate));
  417.         line.push(timeString(item.endDate));
  418.         line.push(item.startDate.isDate ? localeEn.valueTrue : localeEn.valueFalse);
  419.         if (item.alarmOffset) {
  420.             line.push(localeEn.valueTrue);
  421.             var fireTime;
  422.             if (item.alarmRelated == Components.interfaces.calIItemBase.ALARM_RELATED_START) {
  423.                 fireTime = item.startDate.clone();
  424.             } else {
  425.                 fireTime = item.endDate.clone();
  426.             }
  427.             fireTime.addDuration(item.alarmOffset);
  428.             line.push(dateString(fireTime));
  429.             line.push(timeString(fireTime));
  430.         } else {
  431.             line.push(localeEn.valueFalse);
  432.             line.push("");
  433.             line.push("");
  434.         }
  435.         line.push(item.getProperty("CATEGORIES"));
  436.         line.push(item.getProperty("DESCRIPTION"));
  437.         line.push(item.getProperty("LOCATION"));
  438.         line.push((item.privacy=="PRIVATE") ? localeEn.valueTrue : localeEn.valueFalse);
  439.  
  440.         line = line.map(function(v) {
  441.             v = String(v).replace(/"/,'""');
  442.             return '"'+v+'"';
  443.         })
  444.         str = line.join(',')+"\n";
  445.         aStream.write(str, str.length);
  446.     }
  447.  
  448.     return;
  449. };
  450.  
  451. function dateString(aDateTime) {
  452.     return aDateTime.jsDate.toLocaleFormat(localeEn.dateFormat);
  453. }
  454.  
  455. function timeString(aDateTime) {
  456.     return aDateTime.jsDate.toLocaleFormat(localeEn.timeFormat);
  457. }
  458.